package org.yylnb.openplatform.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.yylnb.openplatform.commons.constans.ApiTagInRedis;
import org.yylnb.openplatform.commons.utlis.CommonsResult;
import org.yylnb.openplatform.gateway.api.CacheService;
import org.yylnb.openplatform.gateway.utils.LocalCacheUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * 检查用户剩余的钱，是否满足当前接口的费用需求，进行预先的扣除
 *
 * @author RainLin
 * @date 2020/7/27 - 10:01
 */
public class FeePreFilter extends ZuulFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(FeePreFilter.class);
    private final ObjectMapper objectMapper;
    private final CacheService cacheService;

    public FeePreFilter(ObjectMapper objectMapper, CacheService cacheService) {
        this.objectMapper = objectMapper;
        this.cacheService = cacheService;
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 95;
    }

    @Override
    public boolean shouldFilter() {
        //如果请求拦截了,则不执行,如果是免费接口,则不执行
        RequestContext context = RequestContext.getCurrentContext();
        //如果前置过滤器拦截了,当前过滤器不启用,如果请求是一个非幂等的,当前过滤器也不启用,所以会有多个判断条件,只有全部都符合才启用
        HttpServletRequest request = context.getRequest();
        String method = request.getParameter("method");
        Map<String, String> apiRoutingMap = LocalCacheUtils.getMapping(ApiTagInRedis.API_MAPPING + method);
        //获取是否收费的标记
        String needfee = apiRoutingMap.get("needfee");
        return context.sendZuulResponse() && "1".equalsIgnoreCase(needfee);
    }

    @Override
    public Object run() throws ZuulException {
        //获取到用户剩余的金额,这个金额在账户中而不是在具体的APP中
        RequestContext context = RequestContext.getCurrentContext();
        //如果前置过滤器拦截了,当前过滤器不启用,如果请求是一个非幂等的,当前过滤器也不启用,所以会有多个判断条件,只有全部都符合才启用
        HttpServletRequest request = context.getRequest();
        String app_key = request.getParameter("app_key");
        //1 先根据当前的appkey查下用户id
        Map<String, String> AppInfo = LocalCacheUtils.getMapping(ApiTagInRedis.APP_INFO + app_key);
        String cusId = AppInfo.get("cusId");
        //2 根据用户id查询用户的剩余金额
        //Map<String, String> customerInfo = LocalCacheUtils.getMaping(APICUSTOMER_REDIS_PREKEY + cusId);
        // String money = customerInfo.get("money");//获取用户的钱数,但是不对,因为这个数据是本地数据,其他机器无法发现已经被扣除的数据
        //TODO 上面的几行代码可能会出现空指针,因为指定的服务或者客户信息不在本地存在,所以要注意判空,不存在去缓存或者数据库中查询一下
        //应该从redis缓存中获取
        Map<String, String> moneyMap = cacheService.hGet(ApiTagInRedis.API_CUSTOMER + cusId, "money").getData();
        //获取用户现在的钱数
        String money = moneyMap.get("data");

        //获取到接口需要的费用
        long moneyLong = Long.parseLong(money);
        String method = request.getParameter("method");
        Map<String, String> apiMapping = LocalCacheUtils.getMapping(ApiTagInRedis.API_MAPPING + method);
        String fee = apiMapping.get("fee");
        Long feeLong = Long.parseLong(fee);
        //如果满足 就预先扣除,扣除的结果如果为负数,则代表有多个请求在扣除,还要给人家加回去
        if (moneyLong >= feeLong) {
            //用户当前剩余的钱数比我们要求的要高
            Long data = (Long) cacheService.hIncr(ApiTagInRedis.API_CUSTOMER + cusId, "money", -feeLong).getData();
//            String data = result.get("data");
            //data代表扣除后的用户余额,因为用户可能会在同时请求多个接口,每个都扣钱,可能会出现在判断钱的时候是符合的,但是多个线程同时扣钱后钱就不够了
            //所以我们需要根据返回的结果来对请求是否放行进行判断
            if (data < 0) {
                //代表变成负数了,拦截请求,顺便把钱给人家加回去
                cacheService.hIncr(ApiTagInRedis.API_CUSTOMER + cusId, "money", feeLong);
            } else {
                //把扣除的钱保存到冻结的位置,用于后续返回或者最终抵扣
                //扣除成功,则放入到冻结金额中
                cacheService.hIncr(ApiTagInRedis.API_CUSTOMER + cusId, "freeze", feeLong);
                //记录冻结发生过,方便后续进行判断和使用
                context.put("freeze", feeLong);
                //记录当前的客户,方便后续进行扣钱或者返还使用
                context.put("cusId", cusId);
                //放行
                return null;
            }
            LOGGER.error("还有挺多钱 可以尝试放行{}", data);

        } else {
            //拦截请求
            LOGGER.error("你个穷,抓紧充值");
        }

        //如果代码到这里,代表应该拦截请求
        context.setSendZuulResponse(false);
        HttpServletResponse response = context.getResponse();
        response.setContentType("application/json;charset=utf-8");
        try {
            context.setResponseBody(objectMapper.writeValueAsString(CommonsResult.ERROR.msg("尊敬的用户您好,您剩余金额已不足,请及时充值")));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        return null;
    }

}
